Skip to content

feat: add DeepSeek + GLM Coding Plan providers, fix DeepSeek reasonin…#604

Merged
bao-byterover merged 10 commits intomainfrom
proj/llm-providers-expansion
May 6, 2026
Merged

feat: add DeepSeek + GLM Coding Plan providers, fix DeepSeek reasonin…#604
bao-byterover merged 10 commits intomainfrom
proj/llm-providers-expansion

Conversation

@ngduyanhece
Copy link
Copy Markdown
Contributor

Summary

  • Problem: byterover-cli is missing DeepSeek (one of four user-requested providers) and lacks a way to route through Z.AI's Coding Plan subscription endpoint. Separately, DeepSeek reasoning models were mapped to a think-tags parser that never matched the API's actual reasoning_content field — reasoning output was silently dropped.
  • Why it matters: the four user-requested providers (DeepSeek, MiniMax, Moonshot/Kimi, Z.AI); correct billing for Z.AI Coding Plan subscribers (standard endpoint hits pay-per-token quota); DeepSeek users can finally see reasoning output instead of empty thinking blocks.
  • What changed:
    • New deepseek provider via @ai-sdk/openai-compatible against https://api.deepseek.com/v1 (env DEEPSEEK_API_KEY).
    • New glm-coding-plan provider as a separate ID alongside the existing glm, pointing at https://api.z.ai/api/coding/paas/v4, reusing ZHIPU_API_KEY.
    • Capability fix: deepseek-r1 / deepseek-reasoner now correctly map to reasoningFormat: 'native-field' with reasoningField: 'reasoning_content'.
    • DeepSeek SVG icon (Wikimedia Commons, MIT-licensed, whale-only path with cropped viewBox); GLM Coding Plan reuses the existing zai icon.
    • README provider table bumped 18 → 20.
  • What did NOT change (scope boundary): MiniMax, Moonshot/Kimi, and standard glm were already wired and untouched. No kimi-coding-plan provider, and Moonshot's coding-plan tier uses the same api.moonshot.ai/v1 endpoint as the standard provider. zhipuai-coding-plan (mainland-CN), minimax-coding-plan (Anthropic-compatible), and full models.dev catalog adoption are deferred per plan/llm-providers/DESIGN.md D3/D4.

Type of change

  • Bug fix
  • New feature
  • Refactor (no behavior change)
  • Documentation
  • Test
  • Chore (build, dependencies, CI)

Scope (select all touched areas)

  • TUI / REPL
  • Agent / Tools
  • LLM Providers
  • Server / Daemon
  • Shared (constants, types, transport events)
  • CLI Commands (oclif)
  • Hub / Connectors
  • Cloud Sync
  • CI/CD / Infra

Linked issues

  • Closes #N/A
  • Related #N/A

Root cause (bug fixes only)

  • Root cause: getModelCapabilities() in src/agent/infra/llm/model-capabilities.ts mapped deepseek-r1 / deepseek-reasoner to reasoningFormat: 'think-tags'. DeepSeek's OpenAI-compatible API does not emit <think>...</think> markers — it streams reasoning in a native reasoning_content field. The think-tag scanner never matched, so reasoning content was effectively dropped on the floor.
  • Why this was not caught earlier: DeepSeek had no provider-registry entry, so it was never reachable through brv providers connect and never exercised end-to-end. The capability rule sat in a code path that no integration test could trigger. The fix is forced by adding the provider; this PR closes the loop in one go.

Test plan

  • Coverage added:
    • Unit test
    • Integration test
    • Manual verification only
  • Test file(s):
    • test/unit/agent/llm/model-capabilities.test.ts (new) — 3 assertions for the capability fix
    • test/unit/agent/llm/providers/deepseek.test.ts (new) — 6 assertions for provider-module shape
    • test/unit/agent/llm/providers/glm-coding-plan.test.ts (new) — 7 assertions for provider-module shape
    • test/unit/core/domain/entities/provider-registry.test.ts (extended) — 13 new assertions (6 DeepSeek + 7 GLM Coding Plan)
  • Key scenario(s) covered:
    • Both new providers registered with correct base URL, env var, default model
    • deepseek-reasoner and deepseek-r1 resolve to native-field / reasoning_content; deepseek-chat reports no reasoning
    • glm-coding-plan coexists with glm (regression check that getProviderById('glm') still resolves and the two have distinct base URLs)
    • providerRequiresApiKey('deepseek') and providerRequiresApiKey('glm-coding-plan') both return true

User-visible changes

  • New entries in brv providers connect picker: DeepSeek and GLM Coding Plan (Z.AI).
  • DeepSeek reasoning models (deepseek-reasoner, deepseek-r1) now stream reasoning into the dedicated thinking block instead of silently dropping it.
  • Z.AI Coding Plan subscribers can now route through the dedicated endpoint to consume subscription quota (previously only the standard pay-per-token endpoint was reachable).
  • README provider table updated from 18 to 20 providers.

Evidence

  • Failing test/log before + passing after

TDD sequence (full output in commit history):

# Before implementation — 13 assertions red for the right reason
$ npx mocha "test/unit/core/domain/entities/provider-registry.test.ts" \
            "test/unit/agent/llm/model-capabilities.test.ts" \
            "test/unit/agent/llm/providers/deepseek.test.ts"
  6) Provider Registry > DeepSeek provider > should be registered:
     AssertionError: expected undefined not to be undefined
  ...
  22 passing, 13 failing

# After implementation — full suite green
$ npm test
  7316 passing (19s)
  16 pending

Targeted run after Phase 2 (DeepSeek + GLM Coding Plan):

$ npx mocha "test/unit/core/domain/entities/provider-registry.test.ts" \
            "test/unit/agent/llm/providers/glm-coding-plan.test.ts" \
            "test/unit/agent/llm/providers/deepseek.test.ts" \
            "test/unit/agent/llm/model-capabilities.test.ts"
  51 passing (9ms)

Checklist

  • Tests added or updated and passing (npm test — 7316 passing)
  • Lint passes (npm run lint — 0 errors)
  • Type check passes (npm run typecheck — root + webui clean)
  • Build succeeds (npm run build)
  • Commits follow Conventional Commits format (feat: prefix)
  • Documentation updated (README provider table)
  • No breaking changes (capability fix is a behavior change, but the old code path never matched any real response — see Root cause above)
  • Branch is up to date with main

Risks and mitigations

  • Risk: the hardcoded model list for glm-coding-plan includes glm-5-turbo.
    • Mitigation: if the model 404s, drop it from the array in provider-model-fetcher-registry.ts. Smoke test with a subscription holder is documented as a follow-up in plan/llm-providers/IMPLEMENTATION.md §2.3.
  • Risk: the DeepSeek capability fix changes parsed output for any user who already had deepseek-reasoner accessible via OpenRouter (or any other route that flows through getModelCapabilities).
    • Mitigation: the prior think-tags mapping never matched a real DeepSeek response, so users were not seeing reasoning anyway — this is a fix, not a regression. OpenRouter has its own capability detection path that is unaffected.
  • Risk: standard glm users on the Z.AI Coding Plan subscription might not realize they need to switch to the new glm-coding-plan provider to consume their subscription quota.
    • Mitigation: distinct names in the picker (GLM (Z.AI) vs GLM Coding Plan (Z.AI)) and the description string call out the subscription. README table makes both explicit.

…g detection

Adds two LLM providers to bring byterover-cli to opencode parity for the
four user-requested integrations (DeepSeek, MiniMax, Moonshot/Kimi, Z.AI):

- DeepSeek (deepseek-chat, deepseek-reasoner) via @ai-sdk/openai-compatible
  against https://api.deepseek.com/v1, env DEEPSEEK_API_KEY.
- GLM Coding Plan as a separate provider ID (glm-coding-plan) pointing at
  the Z.AI Coding Plan subscription endpoint (api.z.ai/api/coding/paas/v4),
  reusing ZHIPU_API_KEY. Coexists with the existing 'glm' provider so
  standard pay-per-token users aren't silently switched onto a subscription.

Bug fix: deepseek-r1/deepseek-reasoner were mapped to reasoningFormat
'think-tags' in model-capabilities.ts, but the DeepSeek OpenAI-compatible
API streams reasoning through the native 'reasoning_content' field — the
old path never matched, so reasoning was effectively broken. Now correctly
mapped to native-field with reasoningField: 'reasoning_content'.

MiniMax, Moonshot/Kimi, and standard GLM were already wired and work
unchanged. The Kimi Coding Plan subscription tier uses the same Moonshot
endpoint, so no new provider entry is needed for it (matches opencode's
behavior — opencode also has no kimi-coding-plan provider).

Plan: plan/llm-providers/{DESIGN,IMPLEMENTATION}.md
TDD-authored; 21 new test assertions across registry, capability, and
provider-module shape. Full suite: 7316 passing, 16 pending. 0 lint errors.

Icon asset for DeepSeek sourced from Wikimedia Commons (MIT-licensed),
whale-only path with cropped viewBox. GLM Coding Plan reuses the existing
zai icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Claude finished @ngduyanhece's task in 3m 16s —— View job


PR Review: feat: DeepSeek + GLM Coding Plan providers, fix DeepSeek reasoning

  • Gather context and read changed files
  • Review new provider implementations (DeepSeek, GLM Coding Plan)
  • Review model-capabilities fix
  • Review provider registry changes
  • Review tests
  • Post final review with inline comments and overall score

Summary

Overall score: 4 / 5

This is a well-executed, well-scoped PR. The TDD sequence is evidenced clearly, the bug fix rationale is sound, and the new providers follow the established module pattern precisely. No security issues found. The issues below are all minor maintainability and documentation concerns.


What's Good

  • Capability fix is correct. deepseek-r1/deepseek-reasonernative-field/reasoning_content instead of think-tags is the right call. The old mapping never matched a real API response, so this is a pure fix with no regression risk.
  • Provider module shape is consistent. Both deepseek.ts and glm-coding-plan.ts match the pattern of existing providers (e.g. glm.ts, moonshot.ts) exactly: same field order, same createOpenAICompatible + AiSdkContentGenerator wiring.
  • glm-coding-plan regression guard is solid. The test that asserts getProviderById('glm') still resolves and that the two providers have distinct base URLs (both in provider-registry.test.ts and glm-coding-plan.test.ts) is a good regression check.
  • Icon handling is clean. Reusing the zai icon for glm-coding-plan in provider-icons.ts rather than adding a near-identical duplicate is the right call.
  • Scope boundary is respected. MiniMax, Moonshot, and standard glm are untouched. Deferred work is documented in DESIGN.md/IMPLEMENTATION.md rather than left as code comments.
  • TDD followed. Failing-tests-first evidence is in the PR description; 7316 passing after.

Issues (inline comments posted)

File Line Type Summary
deepseek.ts 24 nitpick Base URL hardcoded twice — divergence risk if one is updated without the other
glm-coding-plan.ts 23 nitpick Same URL duplication
glm-coding-plan.ts 36 nitpick priority: 17.5 — only float priority in the registry; consider shifting integers
model-capabilities.ts 139 question id.includes('r1') matches distilled variants (deepseek-r1-distill-*); worth a test to pin intent
provider-model-fetcher-registry.ts 91 nitpick Coding-plan URL hardcoded independently of PROVIDER_REGISTRY['glm-coding-plan'].baseUrl
provider-model-fetcher-registry.ts 93 suggestion glm-5-turbo is acknowledged as potentially invalid but has no inline comment at the call site
test/unit/.../model-capabilities.test.ts 25 suggestion Add a test for deepseek-r1-distill-llama-70b to document that the r1 substring match is intentional for the whole distilled family

None of these block merging — the most actionable before merge are the glm-5-turbo comment and the distilled-model test.

Comment thread src/agent/infra/llm/providers/deepseek.ts
Comment thread src/agent/infra/llm/providers/glm-coding-plan.ts
Comment thread src/agent/infra/llm/model-capabilities.ts
Comment thread src/server/infra/http/provider-model-fetcher-registry.ts
Comment thread src/server/infra/http/provider-model-fetcher-registry.ts
Comment thread src/agent/infra/llm/providers/glm-coding-plan.ts
Comment thread test/unit/agent/llm/model-capabilities.test.ts
@cuongdo-byterover
Copy link
Copy Markdown
Collaborator

image (UI): We should take a black-and-white version for consistent with the other providers

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Claude finished @cuongdo-byterover's task in 2m 1s —— View job


Updating DeepSeek icon to black-and-white

  • Gather context
  • Read DeepSeek SVG and compare with other provider icons
  • Convert DeepSeek SVG to black-and-white — changed fill="#4d6bfe"fill="#D4D4D4", added width="20" height="20" fill="none" to match all other provider icons
  • Commit created (e47debe) — push blocked by repo permissions (403)

The change is committed locally but the push was denied by GitHub. The diff is:

-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 41.3595">
-  <path fill="#4d6bfe" d="..."/>
+<svg width="20" height="20" viewBox="0 0 56 41.3595" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path fill="#D4D4D4" d="..."/>

To apply this, please apply the same edit to src/webui/assets/providers/deepseek-provider.svg:

  • Change the <svg> opening tag to add width="20" height="20" and fill="none"
  • Change fill="#4d6bfe" to fill="#D4D4D4" on the <path>

Was pointing at the docs landing page (docs.z.ai/devpack/overview) which
buries the actual API-key UI several clicks deep. Direct link to the
key-management page makes onboarding one click.

Reported in ENG-2603 QA round.
…alidating

Validation was sending one chat-completion request keyed on knownModels[0].
For GLM Coding Plan that means glm-4.7 — but the coding-plan tier on Z.AI
doesn't yet serve glm-4.7, only glm-4.5/4.5-flash etc. The endpoint returns
a model-not-found error that the previous handler couldn't disambiguate
from a real auth failure, so a valid key was reported as invalid.

Now validateApiKey iterates through every entry in knownModels:
- 200/202   : key valid, return immediately
- 401       : key invalid, return immediately (do not try further models)
- 403       : key has no permission, return immediately
- 400 / 404 : likely "model not on this tier" — try the next model
- 429 / 5xx : key was accepted (server-side issue), return valid
- network   : try next model; if all fail, return invalid + last error

If knownModels is empty we fall back to a single 'default' model probe.

Adds 9 new test cases covering the first-success path, model-skip path,
auth-error short-circuit, rate-limit handling, server-error handling, all-
models-fail path, and the empty knownModels fallback.

Reported in ENG-2603 QA round.
DeepSeek-R1 (and any provider that emits reasoning_content during a turn)
rejects the next turn's request with "The reasoning_content in the thinking
mode must be passed back to the API" if the assistant message in the
conversation history doesn't carry that prior reasoning. The curate flow
hit this on the second iteration of every multi-step query: turn 1 ran
fine, turn 2 errored before any tool could run.

Three gaps wired:
1. AiSdkContentGenerator.generateContent now copies result.reasoningText
   (Vercel AI SDK already extracts it from reasoning_content) into
   GenerateContentResponse.reasoning. Streaming variant already emitted
   per-delta reasoning chunks; only non-streaming was lossy.
2. AgentLLMService now persists reasoning into the assistant InternalMessage
   in both code paths — non-streaming via response.reasoning, streaming via
   accumulating chunk.reasoning across THINKING chunks.
3. convertAssistantMessage now emits a {type: 'reasoning', text} part on
   the AI-SDK ModelMessage when msg.reasoning is present. The reasoning
   part is placed FIRST in the parts array so providers that key on
   ordering see it as the start of the assistant turn.

Adds 4 regression tests covering: reasoning + text, reasoning + tool-call,
text-only no-regression path, and the empty-message guard.

Reported in ENG-2603 QA round.
The TUI/WebUI provider-setup screen reads apiKeyUrl from
src/server/core/domain/entities/provider-registry.ts, not from the
agent-side provider module. Earlier patch fixed only the agent-side
copy, so the user-visible 'Get your API key at' link still pointed at
docs.z.ai/devpack/overview. This patches the server-side registry to
match.
Six items from the inline review:

1. SVG: remove duplicate <title> on the inner <g> in deepseek-provider.svg
   so screen readers announce the icon name once, not twice.

2. ai-sdk-message-converter: drop the redundant `hasReasoning && msg.reasoning`
   double-guard. The Boolean() check produced no narrowing benefit; a single
   `if (msg.reasoning)` is sufficient.

3. provider-model-fetchers: expand the comment on the validateApiKey
   fall-through branch to call out that ECONNREFUSED / no-response axios
   errors are also treated as "key accepted" via the same path. Documented
   as an optimistic preference for false-positive valid over false-negative
   invalid.

4. ai-sdk-message-converter test: hoist the toModelMessages describe out of
   the toAiSdkTools describe so reporters attribute the new tests to the
   correct parent suite. Added a focused eslint-disable for
   mocha/max-top-level-suites since this file genuinely tests two unrelated
   exports.

5. provider-model-fetchers test: fix indentation of the afterEach() body and
   the closing brace inside the ChatBasedModelFetcher.validateApiKey describe.

6. ai-sdk-message-converter test: add a regression test pinning the
   reasoning-only path (msg.reasoning set, but content/toolCalls absent)
   that returns a parts array with a single reasoning entry.

All 6347 unit tests pass; lint clean; typecheck clean.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Claude finished @cuongdo-byterover's task in 1m 25s —— View job


Updating DeepSeek icon to black-and-white

  • Gather context
  • Read current DeepSeek SVG file
  • Apply black-and-white color change
  • Commit created (4732a7e) — push blocked by repo permissions (403)

The change is committed locally but the push was denied by GitHub. The diff is:

-<svg viewBox="18 21 28 23" xmlns="http://www.w3.org/2000/svg">
+<svg width="20" height="20" viewBox="18 21 28 23" fill="none" xmlns="http://www.w3.org/2000/svg">

The fill color (#D4D4D4) was already correct from the previous update. The only remaining change is adding width="20" height="20" fill="none" to the <svg> opening tag in src/webui/assets/providers/deepseek-provider.svg to match the standard format used by all other provider icons.

To apply this, please edit src/webui/assets/providers/deepseek-provider.svg and replace the opening <svg> tag as shown above. You can also use this link: Fix this →

@ncnthien ncnthien requested review from ncnthien May 5, 2026 09:05
@bao-byterover bao-byterover merged commit 34ec8c6 into main May 6, 2026
6 checks passed
@bao-byterover bao-byterover deleted the proj/llm-providers-expansion branch May 6, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants